﻿using System;
using System.Diagnostics.Contracts;
using O1.IO;

namespace O1.Data.Traversal
{
    [ContractVerification(true)]
    internal static class CellInterpreter
    {
        private static readonly int[] Sizes = InitializeSizesTable();

        private static readonly MarkupBufferHandler[] MarkupTable = InitializeMarkupTable();

        private delegate int MarkupBufferHandler(byte[] buffer, int offset, CellObserver observer);

        public static void Interpret(byte context, InputAdapter adapter, CellObserver observer)
        {
            Contract.Requires(adapter != null);
            Contract.Requires(observer != null);
            Contract.Requires(context < Markup.Count);

            Contract.Assume(MarkupTable.Length == Markup.Count);
            Contract.Assume(Sizes.Length == Markup.Count);
            Contract.Assume(MarkupTable[context] != null);

            var nextSize = Sizes[context];
            
            Contract.Assume(nextSize < BufferCache8K.Size);
            var offset = nextSize > 0 ? adapter.PopOffset(nextSize) : adapter.CurrentOffset;
            MarkupTable[context](adapter.SharedBuffer, offset, observer);
        }

        public static int Interpret(byte context, byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(observer != null);
            Contract.Requires(context < Markup.Count);
            
            Contract.Assume(MarkupTable.Length == Markup.Count);
            Contract.Assume(MarkupTable[context] != null);
            
            return MarkupTable[context](buffer, offset, observer);
        }

        private static int[] InitializeSizesTable()
        {
            var methods = new int[Markup.Count];
            methods[Markup.DateProp] = DateProperty.Size;
            methods[Markup.DateTimeProp] = DateTimeProperty.Size;
            methods[Markup.DecimalProp] = DecimalProperty.Size;
            methods[Markup.EdgeEnd] = 0;
            methods[Markup.HashProp] = HashProperty.Size;
            methods[Markup.InEdge] = Edge.Size;
            methods[Markup.Int16Prop] = Int16Property.Size;
            methods[Markup.Int32Prop] = Int32Property.Size;
            methods[Markup.Int64Prop] = Int64Property.Size;
            methods[Markup.OutEdge] = Edge.Size;
            methods[Markup.StringProp] = StringProperty.FixedSize;
            methods[Markup.VertexEnd] = 0;
            return methods;
        }

        private static MarkupBufferHandler[] InitializeMarkupTable()
        {
            Contract.Ensures(Contract.Result<MarkupBufferHandler[]>().Length  == Markup.Count);

            var methods = new MarkupBufferHandler[Markup.Count];
            methods[Markup.DateProp] = DispatchDateProperty;
            methods[Markup.DateTimeProp] = DispatchDateTimeProperty;
            methods[Markup.DecimalProp] = DispatchDecimalProperty;
            methods[Markup.EdgeEnd] = DispatchEndEdge;
            methods[Markup.HashProp] = DispatchHashProperty;
            methods[Markup.InEdge] = DispatchInEdge;
            methods[Markup.Int16Prop] = DispatchInt16Property;
            methods[Markup.Int32Prop] = DispatchInt32Property;
            methods[Markup.Int64Prop] = DispatchInt64Property;
            methods[Markup.OutEdge] = DispatchOutEdge;
            methods[Markup.StringProp] = DispatchStringProperty;
            methods[Markup.VertexEnd] = DispatchEndVertex;
            return methods;
        }

        private static int DispatchDateProperty(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + DateProperty.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(DateProperty.Read(buffer, offset));
            }

            return Sz.Cell.PropDate;
        }

        private static int DispatchDateTimeProperty(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + DateTimeProperty.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(DateTimeProperty.Read(buffer, offset));
            }
            
            return Sz.Cell.PropDateTime;
        }

        private static int DispatchDecimalProperty(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + DecimalProperty.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(DecimalProperty.Read(buffer, offset));
            }
            
            return Sz.Cell.PropDecimal;
        }

        private static int DispatchEndEdge(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);

            observer.OnEndEdge();
            return 0;
        }

        private static int DispatchHashProperty(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + HashProperty.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(HashProperty.Read(buffer, offset));
            }
            
            return Sz.Cell.PropStringHash;
        }

        private static int DispatchInEdge(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + Edge.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnBeginInEdge(Edge.Read(buffer, offset));
            }
            
            return Edge.Size;
        }

        private static int DispatchInt16Property(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + Int16Property.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(Int16Property.Read(buffer, offset));
            }

            return Int16Property.Size;
        }

        private static int DispatchInt32Property(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + Int32Property.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(Int32Property.Read(buffer, offset));
            }

            return Int32Property.Size;
        }

        private static int DispatchInt64Property(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + Int64Property.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnProperty(Int64Property.Read(buffer, offset));
            }

            return Int64Property.Size;
        }

        private static int DispatchOutEdge(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + Edge.Size < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                observer.OnBeginOutEdge(Edge.Read(buffer, offset));
            }
            
            return Edge.Size;
        }

        private static int DispatchStringProperty(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && buffer != null);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && 0 <= offset);
            Contract.Requires(observer.ObservationState != ObservationInterests.None && offset + StringProperty.FixedSize < buffer.Length);

            if (observer.ObservationState == ObservationInterests.Interested)
            {
                var stringProperty = StringProperty.Read(buffer, offset);
                observer.OnProperty(stringProperty);
                return stringProperty.GetSize();
            }

            return StringProperty.GetSize(buffer, offset);
        }

        private static int DispatchEndVertex(byte[] buffer, int offset, CellObserver observer)
        {
            Contract.Requires(observer != null);

            observer.OnEndVertex();
            return 0;
        }
    }
}